/************************************************************************
Copyright 2003, 2004, 2005, 2006, 2007, 2008 Grid Technology Research Center,
National Institute of Advanced Industrial Science and Technology.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
************************************************************************/

package jp.aist.gtrc.plus.scheduler.util;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Locale;

import org.apache.log4j.Logger;

import jp.aist.gtrc.plus.reserve.ReserveException;
import jp.aist.gtrc.plus.reserve.ReserveLogger;
import jp.aist.gtrc.plus.reserve.ReserveStatus;

import condor.classad.*;

public class ClassAdFuncsForPluS {

	private static final Logger logger = Logger.getLogger(ClassAdFuncsForPluS.class);

	private static double getReal(Expr e) {
		switch (e.type) {
		case Expr.INTEGER:
		case Expr.REAL:
			return e.realValue();
		case Expr.ABSOLUTE_TIME:
		case Expr.RELATIVE_TIME:
			Constant c = (Constant)e;
			return (double)c.milliseconds();
		case Expr.BOOLEAN:
			return e.isTrue() ? 1.0 : 0.0;
		case Expr.UNDEFINED:
		case Expr.ERROR:
		case Expr.STRING:
		default:
			throw new NumberFormatException();
		}
	}

	private static Calendar getCalendar(Expr e) {
		if (e.type == Expr.ABSOLUTE_TIME) {
			Constant c = (Constant)e;
			Calendar cal = Calendar.getInstance();
			cal.setTimeInMillis(c.milliseconds());
			return cal;
		} else {
			return null;
		}
	}

	public static Expr linear(Expr x, Expr x1, Expr y1, Expr x2, Expr y2) {
		/*
		 * return linear function value of f(x) such that f(x1)==y1 && f(x2)==y2
		 * 		f(x) = (y2 - y1) / (x2 - x1) * (x - x1) + y1
		 */
		double xval, x1val, y1val, x2val, y2val;
		try {
			xval = getReal(x);
			x1val = getReal(x1);
			y1val = getReal(y1);
			x2val = getReal(x2);
			y2val = getReal(y2);
		} catch (NumberFormatException e) {
			return Constant.Error;
		}
		if (x2val != x1val) {
			double a = (y2val - y1val) / (x2val - x1val);
			double yval = a * (xval - x1val) + y1val;
			return Constant.getInstance(yval);
		} else {
			return Constant.Undef;
		}
	}

	public static Expr quadratic(Expr x, Expr x1, Expr y1, Expr x2, Expr y2) {
		/*
		 * return quadratic function value of f(x) such that f(x1)==y1 && f(x2)==y2
		 * 		f(x) = (y2 - y1) / (x2 - x1)^2 * (x - x1)^2 + y1
		 */
		double xval, x1val, y1val, x2val, y2val;
		try {
			xval = getReal(x);
			x1val = getReal(x1);
			y1val = getReal(y1);
			x2val = getReal(x2);
			y2val = getReal(y2);
		} catch (NumberFormatException e) {
			return Constant.Error;
		}
		if (x2val != x1val) {
			double dx = (x2val - x1val);
			double a = (y2val - y1val) / (dx * dx);
			double dx2 = (xval - x1val);
			double yval = a * dx2 * dx2 + y1val;
			return Constant.getInstance(yval);
		} else {
			return Constant.Undef;
		}
	}


	private static ReserveLogger rsvLogger = null;

	public static final void setLoggerPath(String path) {

		if (rsvLogger == null) {
			rsvLogger = new ReserveLogger(path);
		}

	}

	private static Expr plus_rsvutil_internal(Expr o, Expr s, Expr e) {

		/*
		 * return { count, durationSum, hourNodeSum } list
		 * of reserves required by owner during start to end.
		 * 	count: number of reserves
		 * 	durationSum: sum of reserve duration in millisec
		 * 	hourNodeSum: sum of (reserved duration[hour] * reserved node number)
		 */
		String owner = o.stringValue();
		Calendar start = getCalendar(s);
		Calendar end = getCalendar(e);
		if (TextUtil.isEmpty(owner)
			|| (start == null) || (end == null)) {
			return Constant.Error;
		}
		if (rsvLogger == null) {
			assert(false);
			return Constant.Error;
		}

		logger.debug(String.format(Locale.US, "owner=%s, start=%tc, end=%tc",
				owner, start, end));

		Collection<ReserveStatus> allStatus;
		try {
			allStatus = rsvLogger.loadAll(owner, start, end, null);
		} catch (ReserveException e1) {
			return Constant.Error;
		}

		int count = 0;
		long durationSum = 0;
		double hourNodeSum = 0.0;
		for (ReserveStatus status : allStatus) {
			count++;
			long duration = TimeUtil.diffOfCal(
					status.getStartTime(), status.getEndTime());
			durationSum += duration;
			hourNodeSum += TimeUtil.milliToHour(duration) * status.getNodeNum();
		}

		ListExpr list = new ListExpr();
		list.add(Constant.getInstance(count));
		list.add(Constant.getInstance(durationSum));
		list.add(Constant.getInstance(hourNodeSum));
		logger.debug("usage sum:" + list.toString());

		return list;

	}

	public static Expr plus_rsvutil(Expr o, Expr s, Expr e) {

		/*
		 * If written in ClassAd below:
		 *      util = plus_rsvutil(PLUS_RSV_OWNER, now - relTime("7d"), now);
		 *		(util[0] >= 100 || util[1] >= relTime("1h") || util[2] >= 100.0) ? 0 : ...
		 * plus_rsvutil() will be called 3 times when evaluating each of
		 * util[0], util[1], and util[2].
		 *
		 * TODO: reduce call?? 3 time calls takes only about 30~50msecs...
		 */
		return plus_rsvutil_internal(o, s, e);

	}

	private static Expr evaluate(Expr baseExpr, String var, String expTxt) {

		RecordExpr rec = new RecordExpr(1);
		rec.insertAttribute(var, baseExpr);
		RecordExpr globalRec = ClassAdEvaluator.getCurrentRecord();
		Iterator<?> ite = globalRec.attributes();
		while (ite.hasNext()) {
			AttrName attr = (AttrName)ite.next();
			String name = attr.rawString();
			rec.insertAttribute(name, globalRec.lookup(name));
		}
		Expr expr = new ClassAdParser(expTxt).parse();
		if (expr != null) {
			Env env = new Env();
			env.push(rec);
			Expr result = expr.eval(env);
			return result;
		} else {
			return Constant.Error;
		}

	}

	private static Expr evaluateList(ListExpr list, String var, String expTxt) {

		ListExpr evalList = new ListExpr();
		int size = list.size();
		for (int i = 0; i < size; i++) {
			Expr entry = list.sub(i);
			Expr result = evaluate(entry, var, expTxt);
			evalList.add(result);
		}

		return evalList;
	}

	public static Expr evaluateList(Expr list, Expr var, Expr expTxt) {

		if ((list.type == Expr.LIST) && (var.type == Expr.STRING)
				&& (expTxt.type == Expr.STRING)) {
			/*
			 * NOTE: If var is "Hello",
			 * var.toString() is "\"Hello\"",
			 * v.stringValue() is "Hello".
			 */
			ListExpr l = (ListExpr)list;
			Constant v = (Constant)var;
			Constant e = (Constant)expTxt;
			return evaluateList(l, v.stringValue(), e.stringValue());
		} else {
			return Constant.Error;
		}

	}

	private static class ExprComp implements Comparator<Expr>, Serializable {
		private static final long serialVersionUID = -6381847119317663303L;
		public int compare(Expr arg0, Expr arg1) {
			try {
				return (int)(getReal(arg0) - getReal(arg1));
			} catch (NumberFormatException e) {
				return arg0.toString().compareTo(arg1.toString());
			}
		}
	}

	private static class ExprRvsComp extends ExprComp {
		private static final long serialVersionUID = -2167008863392570660L;
		public int compare(Expr arg0, Expr arg1) {
			// change sign
			return -super.compare(arg0, arg1);
		}
	}

	private static Expr sort(Expr l, Comparator<Expr> comp) {

		if (l.type != Expr.LIST) {
			return Constant.Error;
		}

		ListExpr argList = (ListExpr)l;
		Expr[] array = new Expr[argList.size()];
		for (int i =0; i < argList.size(); i++) {
			array[i] = argList.sub(i);
		}

		Arrays.sort(array, comp);

		ListExpr sortList = new ListExpr();
		for (Expr e : array) {
			sortList.add(e);
		}
		return (Expr)sortList;

	}

	public static Expr sort(Expr l) {

		return sort(l, new ExprComp());

	}

	public static Expr reverseSort(Expr l) {

		return sort(l, new ExprRvsComp());

	}

}
